#include "glare.gles2.ShaderFactory.h"
#include "glare.gles2.VertexShader.h"
#include "glare.gles2.Driver.h"
#include "glare.gles2.PixelShader.h"
#include "glare.gles2.ShaderInputDeclaration.h"
#include "Glare.Math.Matrix4.h"

namespace Glare {

	GLES2LinkedShader::GLES2LinkedShader()
	{
		_program_id = glCreateProgram();
	}

	GLES2LinkedShader::~GLES2LinkedShader()
	{
		// TODO: shader instances have already been deleted here, handle this better
		glDetachShader(_program_id, _key.VertexShaderId);
		glDetachShader(_program_id, _key.PixelShaderId);
		glDeleteProgram(_program_id);
	}

	bool GLES2LinkedShader::Link(const GLES2ShaderKey& key, string* errors)
	{
		_key = key;

		glAttachShader(_program_id, _key.VertexShaderId);
		glAttachShader(_program_id, _key.PixelShaderId);

		glLinkProgram(_program_id);

		GLint shader_linked;
		glGetProgramiv(_program_id, GL_LINK_STATUS, &shader_linked);

		if (!shader_linked)
		{
			*errors = GLES2ShaderFactory::GetShaderError(_program_id);
			return false;
		}

		glValidateProgram(_program_id);

		GLint shader_valid;
		glGetProgramiv(_program_id, GL_VALIDATE_STATUS, &shader_valid);

		if (!shader_valid)
		{
			*errors = "Error validating shader";
			return false;
		}

		return true;
	}

	void GLES2LinkedShader::Activate()
	{
		glUseProgram(_program_id);
	}

	uint GLES2LinkedShader::GetGLES2AttributeLocation(const string& name)
	{
		return glGetAttribLocation(_program_id, name.c_str());
	}

	uint GLES2LinkedShader::GetGLES2UniformLocation(const string& name)
	{
		return glGetUniformLocation(_program_id, name.c_str());
	}

	GLES2ShaderFactory::GLES2ShaderFactory(GLES2Driver* driver) 
		: ActiveVertexShader(NULL)
		, ActivePixelShader(NULL)
		, ActiveLinkedShader(NULL)
		, _driver(driver)
	{
	}

	GLES2ShaderFactory::~GLES2ShaderFactory()
	{
		ShaderMap::iterator it = _shaders.begin();
		ShaderMap::iterator end = _shaders.end();

		while (it != end)
		{
			delete it->second;
			++it;
		}

		_shaders.clear();
	}

	VertexShader* GLES2ShaderFactory::CreateVertexShader()
	{
		return new GLES2VertexShader(static_cast<GLES2Driver*>(GetDriver()));
	}

	PixelShader* GLES2ShaderFactory::CreatePixelShader()
	{
		return new GLES2PixelShader(static_cast<GLES2Driver*>(GetDriver()));
	}

	void GLES2ShaderFactory::DestroyShader(Shader* shader)
	{
		delete shader;
	}

	ShaderInputDeclaration* GLES2ShaderFactory::CreateInputDeclaration()
	{
		return new GLES2ShaderInputDeclaration(_driver);
	}

	void GLES2ShaderFactory::DestroyInputDeclaration(ShaderInputDeclaration* declaration)
	{
		delete declaration;
	}

	bool GLES2ShaderFactory::GetVariableLocation(const string& variable_name, uint& variable_location, VertexShader* vs, void* hs, void* ds, void* gs, PixelShader* ps)
	{
		GLES2LinkedShader* linked_shader = GetLinkedShader(static_cast<GLES2VertexShader*>(vs), static_cast<GLES2PixelShader*>(ps));

		if (!linked_shader)
			return false;

		variable_location = linked_shader->GetGLES2UniformLocation(variable_name);
		return true;
	}

	bool GLES2ShaderFactory::SetVariableValue(const uint& variable_location, const Matrix4& mat4, const bool& transpose)
	{
		glUniformMatrix4fv(variable_location, 1, transpose, &mat4[0][0]);
		return true;
	}

	Driver* GLES2ShaderFactory::GetDriver() const
	{
		return _driver;
	}
	
	string GLES2ShaderFactory::GetShaderError(GLuint id)
	{
		if (!glIsShader(id))
			return "Error: the given GL name is no shader";

		int error_str_len, error_str_written;
		glGetShaderiv(id, GL_INFO_LOG_LENGTH, &error_str_len);

		char* error_str = new char[error_str_len];
		glGetShaderInfoLog(id, error_str_len, &error_str_written, error_str);

		string result = string(error_str);

		delete[] error_str;
		return result;
	}

	string GLES2ShaderFactory::GetProgramError(GLuint id)
	{
		if (!glIsProgram(id))
			return "Error: the given GL name is no program";

		int error_str_len, error_str_written;
		glGetProgramiv(id, GL_INFO_LOG_LENGTH, &error_str_len);

		char* error_str = new char[error_str_len];
		glGetProgramInfoLog(id, error_str_len, &error_str_written, error_str);

		string result = string(error_str);

		delete[] error_str;
		return result;
	}

	GLES2LinkedShader* GLES2ShaderFactory::GetLinkedShader(GLES2VertexShader* vs, GLES2PixelShader* ps)
	{
		if (!vs || !ps)
			return NULL;

		GLES2ShaderKey key;
		key.VertexShaderId = vs->GetGLES2ShaderId();
		key.PixelShaderId = ps->GetGLES2ShaderId();

		ShaderMap::iterator it = _shaders.find(key);

		GLES2LinkedShader* linked_shader = NULL;

		if (it == _shaders.end())
		{
			linked_shader = new GLES2LinkedShader();

			string errors = "";
			linked_shader->Link(key, &errors);

			if (errors.length() > 0)
				_driver->WriteLog("GLES2ShaderFactory::GetLinkedShader: errors while linking GPU shaders: " + errors, LMT_Error);

			_shaders.insert(ShaderMap::value_type(key, linked_shader));
		}
		else
			linked_shader = it->second;

		return linked_shader;
	}

	void GLES2ShaderFactory::SetShaders()
	{
		// only continue if both, a vertex and a pixel shader are active
		if (!ActiveVertexShader || !ActivePixelShader)
			return;

		GLES2ShaderKey key;
		key.VertexShaderId = ActiveVertexShader->GetGLES2ShaderId();
		key.PixelShaderId = ActivePixelShader->GetGLES2ShaderId();

		ActiveLinkedShader = GetLinkedShader(ActiveVertexShader, ActivePixelShader);
		ActiveLinkedShader->Activate();
	}
}
